package com.smart.config.core;

import com.google.gson.reflect.TypeToken;
import com.smart.config.ConfigProperties;
import com.smart.config.NotifyThreadLocal;
import com.smart.config.constant.RedisConstant;
import com.smart.config.model.ConfigReceiveInfo;
import com.smart.config.model.ConfigVal;
import com.smart.config.model.ListenerModel;
import com.smart.config.utils.ExceptionUtils;
import com.smart.config.utils.GsonTool;
import com.smart.config.utils.IpUtils;
import jakarta.annotation.PostConstruct;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ScanOptions;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;

/**
 * @Author xiaoqiang
 * @Version ConfigManagerImpl.java, v 0.1 2025年03月17日 23:52 xiaoqiang
 * @Description: TODO
 */
public class ConfigManagerImpl implements ConfigManager {

    private static final Logger LOGGER = LoggerFactory.getLogger(ConfigManagerImpl.class);

    private static Map<String, List<ListenerModel>> listeners = new ConcurrentHashMap<>();
    private static RedisTemplate<String, String> redisTemplate;

    private static ConfigProperties properties;


    @PostConstruct
    public void init() {
        // 启动一个线程，定时从redis中获取配置，进行兜底处理，防止redis中配置丢失
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {

                while (true) {
                    // TODO: 从redis中获取配置，进行兜底处理，防止redis中配置丢失

                    listeners.forEach((key, listeners) -> {
                        try {

                            String value = redisTemplate.opsForValue().get(key);
                            if (value == null) {
                                return;
                            }
                            listeners.forEach(l -> innokeValToField(l));
                            //listeners.get(key).forEach(l -> l.getConsumer().accept(value));
                        } catch (Exception ex) {
                            LOGGER.warn("[ConfigManagerImpl#run] pull config from redis error,key={}"
                                    , key, ex);
                        } finally {
                            NotifyThreadLocal.removeException();
                        }
                    });

                    //默认五秒
                    try {
                        int interval = properties.getPullInterval();
                        interval = Math.max(interval, RedisConstant.MIN_PULL_INTERVAL);
                        TimeUnit.SECONDS.sleep(interval);
                    } catch (Exception e) {
                        LOGGER.error("[ConfigManagerImpl#run] pull config from redis error,sleep error", e);
                    }
                }
            }
        });
        thread.setDaemon(true);
        thread.start();
    }

    public ConfigManagerImpl(RedisTemplate<String, String> redisTemplate, ConfigProperties properties) {
        this.redisTemplate = redisTemplate;
        this.properties = properties;
    }

    private void innokeValToField(ListenerModel listenerModel) {
        String realKey = listenerModel.getRealKey();
        Field field = listenerModel.getField();
        Object bean = listenerModel.getBean();
        try {
            Object redisConfigValue = getValue(realKey);
            if (redisConfigValue == null) {
                return;
            }
            Type type = TypeToken.get(field.getGenericType()).getType();
            Object objectVal = GsonTool.fromJson(GsonTool.toJsonString(redisConfigValue), type);

            field.setAccessible(true);
            field.set(bean, objectVal);
        } catch (Exception e) {
            LOGGER.warn("[DynamicValueProcessor#setValToField]setValToField error, realKey:{}, redisValue:{}, field:{}, bean:{}",
                    realKey, field.getName(), bean.getClass().getName(), e);
            throw new RuntimeException(e);
        }
    }


    /**
     * 注册监听器
     *
     * @param key
     * @param listener
     */
    @Override
    public void registerListener(String key, ListenerModel listener) {
        listeners.computeIfAbsent(key, k -> new CopyOnWriteArrayList<>())
                .add(listener);
    }


    /**
     * 通知监听器
     *
     * @param key
     * @param value
     */
    @Override
    public void notifyListener(String key, Long version, String value) {
        List<ListenerModel> targetListeners = listeners.get(key);

        if (CollectionUtils.isEmpty(targetListeners)) {
            LOGGER.warn("[ConfigManagerImpl#notifyListener] notify listener is empty,key={}", key);
            return;
        }
        Exception ex = null;

        //一次异常，不影响其他的接收
        for (ListenerModel targetListener : targetListeners) {
            try {
                innokeValToField(targetListener);
            } catch (Exception e) {
                ex = e;
                LOGGER.error("[ConfigManagerImpl#notifyListener] notify listener error,key={}, listener={}", key, targetListener, e);
            }
        }
        String ip = IpUtils.getIp();
        ConfigReceiveInfo configReceiveInfo =
                ConfigReceiveInfo.builder()
                        .ip(ip)
                        .sucess(ex == null ? true : false)
                        .msg(ex == null ? "success" : ExceptionUtils.createStackTrackMessage(ex))
                        .updateTime(new Date()).build();

        //TODO: 保存配置接收信息到redis中，供监控使用
        String receiveInfoKey = RedisConstant.buildHistoryKeyReceive(key, version);

        redisTemplate.opsForHash().put(receiveInfoKey, ip, GsonTool.toJsonString(configReceiveInfo));
        redisTemplate.expire(receiveInfoKey, 24 * 60 * 60 * 30, TimeUnit.SECONDS);
    }


    public List<ListenerModel> getListeners(String key) {
        return listeners.get(key);
    }

    @Override
    public Object getValue(String key) {
        String configValStr = redisTemplate.opsForValue().get(key);
        if (configValStr == null) {
            return null;
        }
        ConfigVal configVal = GsonTool.fromJson(configValStr, ConfigVal.class);
        return configVal.getContent();
    }
}
